/**
 * @file main.cpp
 * @author BadFatCat0919 (543015378@qq.com)
 * @brief 调味品摄入量智能感知系统开发-ESP8266固件程序入口
 * @date 2022-04-03
 */

/* 头文件 */
#include <Arduino.h>
#include <ESP8266httpUpdate.h>
#include <Adafruit_SSD1306.h>
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#include "NTPClient.h"
#include "PubSubClient.h"
#include "ArduinoJson.h"
#include "HX711.h"
#include <math.h>

/* 固件版本定义 */
#define FIRMWARE_VERSION "V1.2"

/* WiFi参数设置 */
#define WIFI_SSID     "CAUC-WIFI-5G"
#define WIFI_PASSWARD "Xutianze19990919"

/* 巴法云用户私钥设置 */
#define BEMFA_UID     "04f2328bfe7e467d92c156adef0da514"

/* 对象实例定义 */
Adafruit_SSD1306 oled(128, 32);
HX711 scale;
WiFiClient mqttClient_WiFiClient;
PubSubClient mqttClient(mqttClient_WiFiClient);
WiFiUDP NTP_WiFiUDP;
NTPClient NTP(NTP_WiFiUDP, "ntp1.aliyun.com", 8*3600, 60000);

/* 定义LOG上报 */
#define LOG(format, args...) {oled.printf(format, ##args); oled.display(); Serial.printf(format, ##args);}
#define LOG_SERIAL(format, args...) {Serial.printf(format, ##args);}
#define LOG_OLED_CLEAR() {oled.clearDisplay(); oled.display(); oled.setTextSize(1); oled.setCursor(0, 0);}
#define LOG_MQTT(content) mqttClient.publish("LOG", content)

/**
 * @brief 进行OTA固件升级
 */
void OTA_update(){
  WiFiClient UpdateClient;
  LOG_OLED_CLEAR();
  oled.setTextSize(2);
  LOG("OTA update running.");
  LOG_MQTT("OTA update running.");
  switch(ESPhttpUpdate.update(UpdateClient, "http://bin.bemfa.com/b/1BcMDRmMjMyOGJmZTdlNDY3ZDkyYzE1NmFkZWYwZGE1MTQ=OTA.bin")) {
    /*固件升级失败*/
    case HTTP_UPDATE_FAILED:
        LOG_SERIAL("ERROR: OTA UPDATE FILED!");
        break;

    /*不需要升级*/
    case HTTP_UPDATE_NO_UPDATES:
        LOG_SERIAL("No need to update.");
        break;

    /*固件升级成功*/
    case HTTP_UPDATE_OK:
        LOG_SERIAL("OTA update OK.\r\nRebooting...");
        break;
  }
}

/**
 * @brief 订阅的主题有新的消息时的处理函数
 * @param topic 消息主题
 * @param payload 消息内容
 * @param length 消息长度
 */
void mqttClient_Callback(char* topic, byte* payload, unsigned int length) {
  char message[length + 1];
  strncpy(message, (const char *)payload, length);
  message[length] = '\0';
  LOG_SERIAL("MQTT message:\r\n<%s> \"%s\"", topic, message);
  StaticJsonDocument<200> doc;
  if(!deserializeJson(doc, message)) {
    if(!strcmp(topic, "OTA")) {
      if(doc["cmd"]) {
        OTA_update();
      }
    }
  }
}

/**
 * @brief Arduino框架的软硬件初始化接口
 */
void setup()
{
  /* 用于组织系统软硬件初始化流程的有限状态机 */
  struct {
    typedef enum {
      BEGIN,           // 起始
      SERIAL_INIT,     // 串口初始化
      OLED_INIT,       // OLED初始化
      SCALE_INIT,      // 电子秤模块初始化
      SCALE_CHECKING,  // 检测电子秤模块连接状态
      WIFI_INIT,       // WiFi初始化
      WIFI_CONNECTING, // 连接WiFi
      NTP_INIT,        // NTP客户端初始化
      NTP_CONNECTING,  // 连接NTP服务器
      MQTT_INIT,       // MQTT客户端初始化
      MQTT_CONNECTING, // 连接MQTT服务器
      MQTT_SUBSCRIBE,  // MQTT订阅主题
      ERROR,           // 出错
      FINAL,           // 终止
    }StatusType;
    StatusType status = BEGIN;
    StatusType errorStatus = BEGIN;
    bool runFlag = true;

    /**
     * @brief 状态转移
     * @param nextStatus 将要转移的状态
     */
    inline void stateTransition(StatusType nextStatus) {
      status = nextStatus;
    }
    /**
     * @brief 进入出错状态并记录错误来源
     */
    inline void error(void) {
      errorStatus = status;
      stateTransition(ERROR);
    }
  } DFA_Setup;

  while(DFA_Setup.runFlag) {
    switch(DFA_Setup.status) {
    /* 起始 */
    case DFA_Setup.BEGIN:
      DFA_Setup.stateTransition(DFA_Setup.SERIAL_INIT); // 状态转移 => 串口初始化
      break;

    /* 串口初始化 */
    case DFA_Setup.SERIAL_INIT:
      Serial.begin(9600);
      Serial.println();
      DFA_Setup.stateTransition(DFA_Setup.OLED_INIT); // 状态转移 => OLED初始化
      break;

    /* OLED初始化 */
    case DFA_Setup.OLED_INIT:
      oled.begin(SSD1306_SWITCHCAPVCC, 0X3C);
      oled.setCursor(0, 0);
      oled.setTextSize(1);
      oled.setTextColor(SSD1306_WHITE);
      oled.clearDisplay();
      oled.display();
      DFA_Setup.stateTransition(DFA_Setup.SCALE_INIT); // 状态转移 => 电子秤模块初始化
      break;

    /* 电子秤模块初始化 */
    case DFA_Setup.SCALE_INIT:
      scale.begin(D6, D5);
      scale.set_scale(1995.f);
      LOG("System init <1/5>\r\n");
      LOG("Check scale module...\r\n");
      DFA_Setup.stateTransition(DFA_Setup.SCALE_CHECKING); // 状态转移 => 检测电子秤模块连接状态
      break;

    /* 电子秤模块初始化 */
    case DFA_Setup.SCALE_CHECKING:
      if(scale.is_ready()) {
        scale.tare(); // 去皮
        LOG_OLED_CLEAR();
        DFA_Setup.stateTransition(DFA_Setup.WIFI_INIT); // 状态转移 => WiFi初始化
        break;
      }
      delay(100);
      break;

    /* WiFi初始化 */
    case DFA_Setup.WIFI_INIT:
      WiFi.mode(WIFI_STA);
      WiFi.begin(WIFI_SSID, WIFI_PASSWARD);
      WiFi.setAutoConnect(true);
      WiFi.setAutoReconnect(true);
      LOG("System init <2/5>\r\n");
      LOG("WiFi <%s>\r\nConnecting...\r\n", WIFI_SSID);
      DFA_Setup.stateTransition(DFA_Setup.WIFI_CONNECTING);
      break; // 状态转移 => 连接WiFi

    /* 连接WiFi */
    case DFA_Setup.WIFI_CONNECTING:
      if(WiFi.isConnected()) {
        LOG("IP <%s>\r\n", WiFi.localIP().toString().c_str());
        delay(1000);
        LOG_OLED_CLEAR();
        DFA_Setup.stateTransition(DFA_Setup.NTP_INIT); // 状态转移 => NTP客户端初始化
        break;
      }
      delay(500);
      break;

    /* NTP客户端初始化 */
    case DFA_Setup.NTP_INIT:
      NTP.begin();
      LOG("System init <3/5>\r\n");
      LOG("Connect NTP server...\r\n");
      DFA_Setup.stateTransition(DFA_Setup.NTP_CONNECTING); // 状态转移 => 连接NTP服务器
      break;

    /* 连接NTP服务器 */
    case DFA_Setup.NTP_CONNECTING:
      if(NTP.update()) {
        LOG("NTP Time <%s>\r\n", NTP.getFormattedTime().c_str());
        delay(1000);
        LOG_OLED_CLEAR();
        DFA_Setup.stateTransition(DFA_Setup.MQTT_INIT); // 状态转移 => MQTT客户端初始化
        break;
      }
      delay(500);
      break;

    /* MQTT客户端初始化 */
    case DFA_Setup.MQTT_INIT:
      mqttClient.setServer("bemfa.com", 9501).setCallback(mqttClient_Callback);
      LOG("System init <4/5>\r\n");
      LOG("Connect MQTT cloud...\r\n");
      DFA_Setup.stateTransition(DFA_Setup.MQTT_CONNECTING); // 状态转移 => 连接MQTT服务器
      break;

    /* 连接MQTT服务器 */
    case DFA_Setup.MQTT_CONNECTING:
      if(mqttClient.connected()) {
        delay(1000);
        LOG_OLED_CLEAR();
        LOG("System init <5/5>\r\n");
        LOG("MQTT subscribe...\r\n");
        DFA_Setup.stateTransition(DFA_Setup.MQTT_SUBSCRIBE); // 状态转移 => MQTT订阅主题
        break;
      }
      if(!mqttClient.connect(BEMFA_UID)) {
        LOG_OLED_CLEAR();
        LOG("ERROR:\r\nMQTT CONNECT FAILED!\r\nCODE <%d>", mqttClient.state());
        DFA_Setup.stateTransition(DFA_Setup.ERROR);
        DFA_Setup.error(); // 状态转移 => 出错
        break;
      }
      delay(100);
      break;

    /* MQTT订阅主题 */
    case DFA_Setup.MQTT_SUBSCRIBE:
      if(mqttClient.subscribe("OTA") && mqttClient.subscribe("WeChattoESP/app")) {
        delay(1000);
        LOG_OLED_CLEAR();
        DFA_Setup.stateTransition(DFA_Setup.FINAL); // 状态转移 => 终止
        break;
      }
      delay(500);
      break;

    /* 终止 */
    case DFA_Setup.FINAL:
      LOG_OLED_CLEAR();
      LOG("System init succeed.\r\n");
      LOG_MQTT("ESP8266 connected.");
      delay(1000);
      LOG_OLED_CLEAR();
      oled.setTextSize(2);
      oled.setCursor(15, 3);
      oled.printf("EScale");
      oled.setTextSize(1);
      oled.setCursor(92, 10);
      oled.printf(FIRMWARE_VERSION);
      oled.setCursor(38, 23);
      oled.drawFastHLine(0, 20, 128, SSD1306_WHITE);
      oled.printf("Designed by HZT");
      oled.display();
      delay(2000);
      DFA_Setup.runFlag = false;
      LOG_OLED_CLEAR();
      break;

    /* 出错 */
    default:
    case DFA_Setup.ERROR:
      LOG_OLED_CLEAR();
      LOG("ERROR! STATUS<%d>\r\n", DFA_Setup.errorStatus);
      while(true) {} // 进入死循环，看门狗定时器将自动使系统重启
      /* No need to break */;
    }
  }
}

/**
 * @brief Arduino框架的主循环接口
 */
void loop()
{
  /* 用于组织系统工作流程的有限状态机 */
  static struct {
    typedef enum {
      BEGIN,        // 起始
      WIFI_CHECK,   // 检查WiFi连接状态
      NTP_HANDLE,   // NTP时钟同步
      MQTT_HANDLE,  // 处理MQTT服务
      SCALE_HANDLE, // 电子秤称重
      OLED_DISPLAY, // OLED显示
      ERROR,        //出错
    }StatusType;
    StatusType status = BEGIN;
    StatusType errorStatus = BEGIN;

    /**
     * @brief 状态转移
     * @param nextStatus 将要转移的状态
     */
    inline void stateTransition(StatusType nextStatus) {
      status = nextStatus;
    }
    /**
     * @brief 进入出错状态并记录错误来源
     */
    inline void error(void) {
      errorStatus = status;
      stateTransition(ERROR);
    }
  } DFA_MainLoop;

  float scaleResult = 0;
  long mill = 0;
  static long lastSendMill = 0;
  static bool mqttSendScale = true;
  char weightMsg[] = "00000.0";

  switch (DFA_MainLoop.status)
  {
  /* 起始 */
  case DFA_MainLoop.BEGIN:
    oled.clearDisplay();
    DFA_MainLoop.stateTransition(DFA_MainLoop.WIFI_CHECK); // 状态转移 => 检查WiFi连接状态
    break;

  /* 检查WiFi连接状态 */
  case DFA_MainLoop.WIFI_CHECK:
    if(!WiFi.isConnected()) {
      LOG_OLED_CLEAR();
      LOG("WiFi DISCONNECTED!\r\n");
      DFA_MainLoop.error(); // 状态转移 => 出错
      delay(3000);
      break;
    }
    DFA_MainLoop.stateTransition(DFA_MainLoop.NTP_HANDLE); // 状态转移 => NTP时钟同步
    break;

  /* NTP时钟同步 */
  case DFA_MainLoop.NTP_HANDLE:
    if(!NTP.update()) {
      DFA_MainLoop.error(); // 状态转移 => 出错
      break;
    }
    DFA_MainLoop.stateTransition(DFA_MainLoop.MQTT_HANDLE); // 状态转移 => 处理MQTT服务
    break;

  /* 处理MQTT服务 */
  case DFA_MainLoop.MQTT_HANDLE:
    if(!mqttClient.loop()) {
      DFA_MainLoop.error(); // 状态转移 => 出错
      break;
    }
    DFA_MainLoop.stateTransition(DFA_MainLoop.SCALE_HANDLE); // 状态转移 => 电子秤称重
    break;

  /* 电子秤称重 */
  case DFA_MainLoop.SCALE_HANDLE:
    scaleResult = scale.get_units();
    if(scaleResult <= 0) {
      scaleResult = 0;
    }
    oled.setTextSize(3);
    oled.setCursor(0, 3);
    oled.printf("%4.1fg", scaleResult >= 0 ? scaleResult : 0);
    sprintf(weightMsg, "%.1f", scaleResult);
    mill = millis();
    if(mill - lastSendMill >= 500) { // 每500毫秒发送一次
      lastSendMill = mill;
      mqttSendScale = true;
    }
    if(mqttSendScale) {
      mqttSendScale = !mqttClient.publish("ESPtoWeChat", weightMsg);
    }
    DFA_MainLoop.stateTransition(DFA_MainLoop.OLED_DISPLAY); // 状态转移 => OLED显示
    break;

  /* OLED显示 */
  case DFA_MainLoop.OLED_DISPLAY:
    oled.display();
    DFA_MainLoop.stateTransition(DFA_MainLoop.BEGIN); // 状态转移 => 起始
    break;
  
  /* 出错 */
  default:
  case DFA_MainLoop.ERROR:
    LOG_OLED_CLEAR();
    LOG("ERROR! STATUS<%d>\r\n", DFA_MainLoop.errorStatus);
    while(true) {} // 进入死循环，看门狗定时器将自动使系统重启
    /* No need to break */;
  }
}
